using System;
using System.Collections.Generic;
using System.Reflection;

namespace Weever.Json.Serialization.v1
{
    class TypeReflector
    {
        private Dictionary<Type, object> _wellKnownTypes = new Dictionary<Type, object>();

        public TypeReflector()
        {
            Type[] types = new Type[]
                {
                    typeof(string),
                    typeof(int),
                    typeof(float),
                    typeof(double),
                    typeof(short),
                    typeof(bool),
                    typeof(Int64),
                    typeof(Nullable<int>),
                    typeof(Nullable<float>),
                    typeof(Nullable<double>),
                    typeof(Nullable<short>),
                    typeof(Nullable<bool>),
                    typeof(Nullable<Int64>)
                };
            foreach (Type type in types)
            {
                _wellKnownTypes.Add(type, null);
            }
        }

        public List<Type> WellKnownTypes
        {
            get
            {
                return new List<Type>(_wellKnownTypes.Keys);
            }
        }

        public List<Type> GetAllTypes(Type type)
        {
            Dictionary<Type, object> types = new Dictionary<Type, object>();
            FillMembersType(type, types);
            return new List<Type>(types.Keys);
        }

        private void FillMembersType(Type type, Dictionary<Type, object> types)
        {
            if (types.ContainsKey(type)
                || _wellKnownTypes.ContainsKey(type))
                return;

            types.Add(type, null);

            if (type.IsEnum)
                return;

            if (type.IsArray)
            {
                FillMembersType(type.GetElementType(), types);
                return;
            }

            BindingFlags bf = BindingFlags.Default
                              | BindingFlags.Instance
                              | BindingFlags.Public
                              | BindingFlags.FlattenHierarchy;
                              //| BindingFlags.NonPublic;
            
            FillMembersType(type.GetFields(bf), types);
            FillMembersType(type.GetProperties(bf), types);
        }

        private void FillMembersType(ICollection<FieldInfo> members, Dictionary<Type, object> types)
        {
            foreach (FieldInfo info in members)
            {
                if (types.ContainsKey(info.FieldType) 
                 || _wellKnownTypes.ContainsKey(info.FieldType))
                    continue;

                types.Add(info.FieldType, null);
                FillMembersType(info.FieldType, types);
            }
        }

        private void FillMembersType(ICollection<PropertyInfo> members, Dictionary<Type, object> types)
        {
            foreach (PropertyInfo info in members)
            {
                if (types.ContainsKey(info.PropertyType)
                 || _wellKnownTypes.ContainsKey(info.PropertyType))
                    continue;

                types.Add(info.PropertyType, null);
                FillMembersType(info.PropertyType, types);
            }
        }

        public ReflectedMemberInfo[] GetSerializableMembers(Type type)
        {
            object[] policies = type.GetCustomAttributes(typeof (SerializationPolicyAttibute), true);

            SerializationPolicy policy = SerializationPolicy.AllPosibleMembers;
            if (policies.Length > 0)
                policy = ((SerializationPolicyAttibute) policies[0]).Policy;

            BindingFlags flags = BindingFlags.Public
                                 | BindingFlags.Instance
                                 | BindingFlags.FlattenHierarchy
                                 | BindingFlags.GetField
                                 | BindingFlags.GetProperty;
                                 //| BindingFlags.NonPublic;

            MemberInfo [] infos = FilterMembers(type.GetMembers(flags), policy);

            MemberTypes fieldOrProperty = MemberTypes.Property | MemberTypes.Field;

            List<ReflectedMemberInfo> members = new List<ReflectedMemberInfo>();

            foreach (MemberInfo info in infos)
                if ((info.MemberType & fieldOrProperty) != 0)
                    members.Add(ReflectedMemberInfo.CreateFromMemberInfo(info));

            return members.ToArray();
        }

        private MemberInfo[] FilterMembers(MemberInfo[] infos, SerializationPolicy policy)
        {
            if (policy == SerializationPolicy.OnlyMarketMembers)
            {
                List<MemberInfo> members = new List<MemberInfo>();
                foreach (MemberInfo memberInfo in infos)
                {
                    if (  memberInfo.GetCustomAttributes(typeof(CamelCaseAttribute), true).Length > 0
                       || memberInfo.GetCustomAttributes(typeof(JsonNameAttribute), true).Length > 0)
                        members.Add(memberInfo);
                }
                return members.ToArray();
            }

            return infos;
        }

        internal static bool TypeIsCollection(Type type, out Type elementType)
        {
            elementType = null;
            if (type.IsArray)
            {
                elementType = type.GetElementType();
                return true;
            }

            if (type.IsGenericType)
            {
                if (type.GetGenericTypeDefinition().GetInterface("ICollection`1") != null
                 || type.GetGenericTypeDefinition().FullName.Equals("System.Collections.Generic.ICollection`1"))
                {
                    elementType = type.GetGenericArguments()[0];
                    return true;
                }
            }
            return false;
        }

        internal static bool TypeIsDictionary(Type type, out Type keyType, out Type valueType)
        {
            keyType = null;
            valueType = null;
            if (type.IsGenericType)
                if (type.GetGenericTypeDefinition().GetInterface("IDictionary`2") != null
                 || type.GetGenericTypeDefinition().FullName.Equals("System.Collections.Generic.IDictionary`2"))
                {
                    Type[] genericArgs = type.GetGenericArguments();
                    keyType = genericArgs[0];
                    valueType = genericArgs[1];
                    return true;
                }

            return false;
        }

    }
}
